package org.liveboardgames.agricola.access.hibernate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.liveboardgames.agricola.access.ICardDao;
import org.liveboardgames.agricola.domain.card.ActionCard;
import org.liveboardgames.agricola.domain.card.BakingImprovementCard;
import org.liveboardgames.agricola.domain.card.Card;
import org.liveboardgames.agricola.domain.card.CookingImprovementCard;
import org.liveboardgames.agricola.domain.card.ImprovementCard;
import org.liveboardgames.agricola.domain.card.ReferenceBakingImprovementCard;
import org.liveboardgames.agricola.domain.card.ReferenceCard;
import org.liveboardgames.agricola.domain.card.ReferenceCookingImprovementCard;
import org.liveboardgames.agricola.domain.card.ReferenceImprovementCard;
import org.liveboardgames.agricola.model.enums.ChangingTypeEnum;
import org.liveboardgames.agricola.model.enums.GameTypeEnum;
import org.liveboardgames.agricola.model.structure.GameLoadVisitor;
import org.liveboardgames.agricola.model.structure.Visitor;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class CardHibernateDao extends HibernateDaoSupport implements ICardDao {

  @SuppressWarnings(value = "unchecked")
  public List<ActionCard> getStartingActionCards(GameTypeEnum gameType, int numberOfPlayers) {
    String queryString =
        "from ReferenceActionCard card where card.familyType = :gameType and card.numberOfPlayers = :numberOfPlayers";
    String[] namedParams = { "gameType", "numberOfPlayers" };
    Object[] paramValues = { gameType, numberOfPlayers };
    getHibernateTemplate().setCacheQueries(true);

    List cards = getHibernateTemplate().findByNamedParam(queryString, namedParams, paramValues);
    List instanceCards = getInstanceCards(cards);

    return instanceCards;
  }

  private List getInstanceCards(List cards) {
    List instanceCards = new ArrayList();

    for (Iterator iterator = cards.iterator(); iterator.hasNext();) {
      ReferenceCard card = (ReferenceCard)iterator.next();
      Card instanceCard = new ActionCard();
      instanceCard.setRefCard(card);
      instanceCards.add(instanceCard);
    }
    // getHibernateTemplate().flush();

    return instanceCards;
  }

  public Set<Card> getBeggingCards(int number) {
    String queryString = "from ReferenceCard card where card.cardName = :cardName";
    String[] namedParams = { "cardName" };
    Object[] paramValues = { "BEGGING_CARD" };
    
    getHibernateTemplate().setCacheQueries(true);

    ReferenceCard referenceBeggingCard = (ReferenceCard)getHibernateTemplate().findByNamedParam(queryString, namedParams, paramValues).get(0);
    
    Set<Card> cards = new HashSet<Card>();
    for (int i = 0; i < number; i++) {
      Card instanceCard = new ActionCard();
      instanceCard.setRefCard(referenceBeggingCard);
      getHibernateTemplate().save(instanceCard);
      cards.add(instanceCard);
    }
    
    return cards;
  }

  @SuppressWarnings(value = "unchecked")
  public List<ActionCard> getStaticActionCards(GameTypeEnum gameType) {
    // Retrieve the "family" static cards
    String familyQuery =
        "from ReferenceActionCard card where card.familyType = :gameType and card.changingType = :changingType";
    String[] namedParams = { "gameType", "changingType" };
    Object[] paramValues = { gameType, ChangingTypeEnum.STATIC };
    getHibernateTemplate().setCacheQueries(true);
    List familyCards = getHibernateTemplate().findByNamedParam(familyQuery, namedParams, paramValues);

    // Retrieve the common static cards
    String commonQuery =
        "from ReferenceActionCard card where card.familyType = :gameType and card.changingType = :changingType";
    String[] namedParams2 = { "gameType", "changingType" };
    Object[] paramValues2 = { GameTypeEnum.COMMON, ChangingTypeEnum.STATIC };
    List commonCards = getHibernateTemplate().findByNamedParam(commonQuery, namedParams2, paramValues2);

    // Finally, create a list with everything
    List<ActionCard> allCards = new ArrayList<ActionCard>();
    allCards.addAll(familyCards);
    allCards.addAll(commonCards);

    List instanceCards = getInstanceCards(allCards);

    // Make sure the list of cards is sorted by the IDs of the cards
    Collections.sort(instanceCards);

    return instanceCards;
  }

  @SuppressWarnings(value = "unchecked")
  public List<ActionCard> getActionCardsForPeriod(int i) {
    String queryString =
        "from ReferenceActionCard card where card.familyType = :gameType and card.changingType = :changingType and card.period = :period";
    String[] namedParams = { "gameType", "changingType", "period" };
    Object[] paramValues = { GameTypeEnum.COMMON, ChangingTypeEnum.CHANGING, i };
    getHibernateTemplate().setCacheQueries(true);

    List actionCards = getHibernateTemplate().findByNamedParam(queryString, namedParams, paramValues);

    List instanceCards = getInstanceCards(actionCards);
    return instanceCards;
  }

  @SuppressWarnings(value = "unchecked")
  public Set<ImprovementCard> getAllMajorImprovements() {
    String queryString = "from ReferenceImprovementCard card where card.category = :category";
    String[] namedParams = { "category" };
    Object[] paramValues = { "MAJOR" };
    getHibernateTemplate().setCacheQueries(true);

    List improvementsAsList = getHibernateTemplate().findByNamedParam(queryString, namedParams, paramValues);
    Set majorImprovementCards = null;
    if (improvementsAsList != null) {
      majorImprovementCards = new HashSet();
      for (Object refObject : improvementsAsList) {
        ReferenceImprovementCard ref = (ReferenceImprovementCard)refObject;
        ImprovementCard improvementCard = getCard(ref);
        improvementCard.setRefCard(ref);
        majorImprovementCards.add(improvementCard);
      }
    }
    return majorImprovementCards;
  }

  private ImprovementCard getCard(ReferenceImprovementCard ref) {
    ImprovementCard card = null;

    if (ref instanceof ReferenceCookingImprovementCard) {
      card = new CookingImprovementCard();
    }
    else if (ref instanceof ReferenceBakingImprovementCard) {
      card = new BakingImprovementCard();
    }
    else {
      card = new ImprovementCard();
    }

    return card;
  }

  public Card load(Long cardId) {
    Card card = (Card)getHibernateTemplate().load(Card.class, cardId);
    Visitor visitor = new GameLoadVisitor();
    card.acceptVisit(visitor);
    return card;
  }

  public void save(Card card) {
    getHibernateTemplate().saveOrUpdate(card);
  }
}
